Utilisation de FFMPEG dans le projet Meteorix¶
### Chargement des librairies
from IPython.display import Image
Objectif¶
Notre objectif est d'adresser une puce matérielle pour encoder une vidéo à partir d'images brutes. En effet, sur le nano-satellite nous aurons uniquement une puce pour faire l'encodage de la vidéo. Pour tester cela nous allons utiliser FFMPEG avec différentes options pour voir comment sont encodés les images.
En testant différentes options, nous allons essayer de déterminer quels sont celles optimales pour capturer le météore.
1 - Procédure pour utiliser la puce matériel¶
1.1 - Copier les images sur la machine EM780¶
Nous avons des vidéos de météores en local, nous allons copier les vidéos le disque dur de l'EM780 (une mcahine au LIP6) qui à des encodeurs similaires à ceux que l'on aura sur le satellite.
Pour se connecter à la machine : ssh em780.mono.lip6.ext
Ensuite on copie les vidéos de la manière suivante :
- (en local)
scp fichier-sur-la-machine em780.mono.lip6.ext:~/Les vidéos vont être copiés sur le scracth de la machine, on les déplace sur notre environnement : - (sur em780)
mv v03.mp4 /scratch/renaudc-nfsPuis on va dans notre dossiercd /scratch/renaudc-nfspour travailler faire les expérimentations.
De plus, FFMPEG est déjà installé sur la machine, on pourra donc utiliser directement les commandes FFMPEG depuis la machine.
1.2 - Transformer une vidéo compressé au format ppm¶
Pour le moment nous avons des vidéos dans des formats compressés, or la caméra du satellite récupère des images au format ppm (format brut). Nous voulons donc transformer nos vidéos compressés en images au format ppm pour ensuite donner ces image à la puce afin qu'elle les compresse.
La commande suivante permet de le faire en créant un repertoire les images : mkdir -p output_images && ffmpeg -i video.mpg output_images/image%d.ppm
1.3 - Encoder les images ppm¶
Il y a deux GPU sur l'EM780, nous allons essayer d'adresser les duex
- adresser la puce sur le GPU embarqué AMD Radeon 780M (avec VAAPI)
- adresser la puce sur le GPU externe NVidia GeForce RTX 3090 (dans ce cas il faut utiliser NVENC)
L'option 1 est la meilleure car la puce est sur le SoC, ce qui sera probablement plus proche de la réalité dans un nano-satellite. L'option 2 est certainement plus facile car NVENC est bien supporté et documenté en général
2 - Utilisation des GPUs¶
2.1 -NVidia GeForce RTX 3090¶
On peut encoder avec la commande : ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset fast -b:v 5M compressed_video.mp4 -loglevel verbose
-hwaccel cuda : Active l'accélération matérielle CUDA pour optimiser le traitement.
-c:v h264_nvenc : Utilise NVENC pour encoder la vidéo au format H.264.
-preset : permet de changer la qualité de compression, on met slow pour mieux voir l'utilisation du gpu.
On peut stocker regarder l'utilisation en parralèle avec nvidia-smi
On regarde l'utilisation du gpu avant et pendant l'éxécution de ffmpeg :
Sans processus¶
Image("../utilisationGPU/nvidia-smi_avant.png")
Avec Processus¶
Image("../utilisationGPU/nvidia-smi_pendant.png")
On remarque une augmentation de 80 watts, cette augmentation varie un peu à chaque éxécution, on peut tout de même en conclure que le GPU est bien utilisé
On peut aussi utiliser l'utilisation de nvtop pour voir l'utilisation du gpu en direct
GPU en direct¶
Image("../utilisationGPU/ffmpegNVIDIA_ecran.png")
Les deux pics correspondent à l'éxécution des commandes ffmpeg, de plus on voit bien que c'est le GPU NVIDIA que nous avons utilisé.
Avec htop on peut aussi regarder l'utilisation du CPU
Utilisation du CPU¶
Image("../utilisationGPU/ffmpegNVIDIA_commande.png")
On remarque donc que ffmpeg utilise aussi les capcités du CPU
3.2 AMD Radeon 780M¶
Pour utiliser le GPU AMD Radeon on va essayer d'utiliser la bibliothèque VAAPI
Avec la commande suivante on va utiliser AMD Radeon 780M
ffmpeg -vaapi_device /dev/dri/renderD128 -i output_images/image%d.ppm -vf 'format=nv12,hwupload' -c:v h264_vaapi -qp 24 -preset fast compressed_video.mp4
Explication des options :
-vaapi_device /dev/dri/renderD128 : Spécifie le périphérique VAAPI à utiliser.
-vf 'format=nv12,hwupload' : Convertit les images au format compatible VAAPI (nv12) et les télécharge sur le GPU.
-c:v h264_vaapi : Utilise VAAPI pour encoder en H.264.
-qp 24 : Définit la qualité (QP). Ajustez entre 20 (qualité élevée) et 35 (compression élevée).
-preset fast : Définit une vitesse d'encodage optimisée.
On a cette erreur :
Failed to initialise VAAPI connection: -1 (unknown libva error).
Device creation failed: -5.
Failed to set value '/dev/dri/renderD128' for option 'vaapi_device': Input/output error\
L'utilisation de l'AMD Radeon n'as pas fonctionné du fait de bibliothèques trop compliqué à installer
Maintenant que nous avons configuré l'EM780, nous allons appliquer notre algorithme de filtre sur les vidéos encodées à l'aide du GPU avec différent paramètres d'encodage.
Pour utiliser mve sur l'EM780, on a commencé par créer un environnement virtuelle python.
Création : python3 -m venv venv-mvextractor
Activation : source venv-mvextractor/bin/activate
Ensuite il faut installer le fichier requirements.txt avec pip3 install -r requirements.txt, puis visualiser les versions des librairies avec pip3 list :
- GPUtil==1.4.0
- joblib==1.4.2
- motion-vector-extractor==1.1.0
- numpy==2.1.3
- opencv-python==4.10.0.84
- psutil==6.1.1
- scikit-learn==1.5.2
- scipy==1.14.1
- threadpoolctl==3.5.0
3.2 Lancement de mve sur une vidéo¶
On peut lancer notre code sur une vidéo avec python3 src/mvextractor/__main__.py v03.mp4 --dump, les frames vont être stockées dans le dossier outputs_frames/
On peut ensuite comparer les frames de la vidéo que nous avons transformé en images ppm puis compressé avec le GPU Nvidia avec la vidéo originale.
Comme on ne peut pas visualiser directement les images sur l'EM780, on les transfère sur notre pc depuis la machine à distance.
Pour copier les images de em780 jusqu'en local on suit ces deux étapes :
- depuis /scratch/
-nfs : cp -r outputs_frames/ /nfs/users/<username>-nfs - en local :
scp -r front.mono.lip6.ext:~/outputs_frames/ chemin-en-local/
En local¶
Image("..//outputs_frames/Mission Chiba original sur l'EM780 vecteur/frames/frame-102.jpg")
Sur l'EM780¶
Image("..//outputs_frames/Mission Chiba video compresser par le GPU sur l'EM780 vecteur/frames/frame-102.jpg")
La première image est issu de la vidéo originale et la seconde de la vidéo compressé sur l'em780. On voit bien que mve à reussi à capturer le météore sur la vidéo originale alors que pas du tout sur la seconde vidéo.
Les filtres appliquées sont peut être trop fort, ou l'image est simplement compressé différement.
Par la suite nous n'allons aplliqué aucun filtre pour être sur de pouvoir attraper le météore.
4. Comparaisson python et C++¶
Avec vidéo de Big Buck Bunny en h264 Sans stockage des frames et vecteurs :
- Python : Temps moyen (dt): 0.008996874225519601 secondes
- C++ : Temps moyen (dt) : 0.00733995 secondes
Avec stockage :
- Python : Temps moyen (dt) 0.035819509320476714 secondes
- C++ : Temps moyen (dt) : 0.0168462 secondes
En comparant l'utilisation de python et C++ sur l'EM780, on remarque que le code python est plus lent notamment quand il faut stocker les frames avec les vecteurs dessus.
5. Modification des paramètres d'encodage¶
On a vu qu'en encodant la vidéo avec le GPU sur l'em780 les vecteurs vitesses trouvés n'étaient pas bons. Nous allons donc essayer de jouer sur les paramètres d'encodage pour obtenir un résultat satisfaisant. Nous pouvons jouer sur :
- -c:v h264_nvenc (h264) -c:v hevc_nvenc (h265)
- -framerate : Nombre d'image par seconde (par défaut, 25)
- -preset : Permet de changer la compression mais change la qualité en conséquent (plus le preset est faible, meilleure la compression sera)
- -g : change la fréquence des images clés (par défaut, framerate*10)
- -b:v : pour changer le bitrate
- -cq 23 avec -rc vbr permet d'adapter la compression selon la qualité de l'image et de donner plus de bits aux parties en mouvements
- -pix_fmt yuv420p, yuv444p, gray
5.1 Déroulé¶
- On commence par compresser les images avec les commandes décrites dans Expérimentations
- On lance Motion Vector Extractor sur chaque vidéo
- On copie les données obtenues en local
5.2 Commandes expérimentales¶
| Numéro | Vidéo | Commande | Framerate | Preset | images clés | bitrate | encodage | pix_fmt | Capture du météore ? |
|---|---|---|---|---|---|---|---|---|---|
| 1 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset fast -b:v 5M compressed_video.mp4 -loglevel verbose | 30 | fast | default | 5M | h264 | défaut | |
| 2 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset fast -b:v 5M -g 10 compressed_video.mp4 -loglevel verbose | 30 | fast | 10 | 5M | h264 | défaut | |
| 3 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset slow -b:v 5M -g 10 compressed_video.mp4 -loglevel verbose | 30 | slow | 10 | 5M | h264 | défaut | |
| 4 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset slow -b:v 5M -g 5 compressed_video.mp4 -loglevel verbose | 30 | slow | 5 | 5M | h264 | défaut | |
| 5 | v03 | ffmpeg -hwaccel cuda -i output_images/image%d.ppm -c:v h264_nvenc -preset slow -b:v 5M -g 10 compressed_video.mp4 -loglevel verbose | défaut | slow | 10 | 5M | h264 | défaut | |
| 6 | v03 | ffmpeg -hwaccel cuda -i output_images/image%d.ppm -c:v h264_nvenc -preset p7 -b:v 5M -g 10 compressed_video.mp4 -loglevel verbose | défaut | p7 | 10 | 5M | h264 | défaut | |
| 7 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset p7 -cq 30 -rc vbr -g 10 -pix_fmt yuv420p -b:v 2M compressed_video.mp4 -loglevel verbose | 30 | p7 | 10 | variable | h264 | yuv420p | |
| 8 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset p7 -cq 30 -rc vbr -g 10 -pix_fmt gray -b:v 2M compressed_video.mp4 -loglevel verbose | 30 | p7 | 10 | variable | h264 | gray | |
| 9 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset slow -b:v 5M -g 15 compressed_video.mp4 -loglevel verbose | 30 | slow | 15 | 5M | h264 | défaut | |
| 10 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v hevc_nvenc -preset slow -b:v 5M -g 10 compressed_video.mp4 -loglevel verbose | 30 | slow | 10 | 5M | h265 | défaut | |
| 11 | v03 | ffmpeg -hwaccel cuda -framerate 30 -i output_images/image%d.ppm -c:v h264_nvenc -preset p7 -cq 30 -rc vbr -g 10 -pix_fmt yuv444p -b:v 2M compressed_video.mp4 -loglevel verbose | 30 | p7 | 10 | variable | h264 | yuv444p | |
| 12 | v00 | ffmpeg -hwaccel cuda -framerate 30 -i output_images00/image%d.ppm -c:v h264_nvenc -preset slow -b:v 5M -g 10 compressed_video.mp4 -loglevel verbose | 30 | slow | 10 | 5M | h264 | défaut |
5-2 Image de météore taille originale¶
On commence par étudier la vidéo au dimension originale, pour cela on affiche une frame ou il y le météore
import os
import re
from PIL import Image
from IPython.display import display
# Dossier racine contenant les sous-dossiers
root_folder = "../outputs_frames/"
# Regex pour extraire le numéro au début du dossier parent (ex: "123out-" → "123")
pattern = re.compile(r"^(\d+)out-")
# Liste des images "frame-102.jpg"
image_files = []
for subdir, _, files in os.walk(root_folder):
parent_folder = os.path.basename(os.path.dirname(subdir)) # Ex: "123out-blabla"
folder_name = os.path.basename(subdir) # Ex: "frames"
# Vérifier si le dossier parent correspond au pattern et que nous sommes dans "frames/"
match = pattern.match(parent_folder)
if match and folder_name == "frames":
if int(match.group(1)) <int("12") :
frame_path = os.path.join(subdir, "frame-102.jpg") # Construire le chemin cible
if int(match.group(1))==int("12"):
frame_path = os.path.join(subdir, "frame-59.jpg")
if os.path.exists(frame_path): # Vérifier si le fichier existe
image_files.append((match.group(1), frame_path)) # Stocker (numéro dossier, chemin image)
# Trier les images par numéro de dossier (au cas où)
image_files.sort(key=lambda x: int(x[0]))
# Affichage des images avec PIL
if not image_files:
print("Aucune image 'frame-102.jpg' trouvée.")
else:
for folder_num, img_path in image_files:
print(f"Expériences : {folder_num}") # Affiche le numéro du dossier
img = Image.open(img_path) # Ouvrir l'image avec PIL
display(img) # Afficher dans Jupyter
Expériences : 1
Expériences : 2
Expériences : 3
Expériences : 4
Expériences : 5
Expériences : 6
Expériences : 7
Expériences : 8
Expériences : 9
Expériences : 10
Expériences : 11
Expériences : 12
Le météore est capturé pour la frame 102 avec la commande 3 à 9 et 11. L'encodage h265 n'est pas supporté par le GPU
6-3 Image de météore taille réduite¶
On a maintenant conservé uniquement la partie de la vidéo avec le météore avec la commande ffmpeg -i input.mp4 -vf "crop=448:320:0:0" output.mp4. Les deux premiers nombres correspondent à la taille de l'image et les deux suivants à la position du coin en haut à gauche.
import os
import re
from PIL import Image
from IPython.display import display
# Dossier racine contenant les sous-dossiers
root_folder = "../outputs_frames/"
# Regex pour extraire le numéro au début du dossier parent (ex: "123out-" → "123")
pattern = re.compile(r"^(\d+)crop_out-")
# Liste des images "frame-102.jpg"
image_files = []
for subdir, _, files in os.walk(root_folder):
parent_folder = os.path.basename(os.path.dirname(subdir)) # Ex: "123out-blabla"
folder_name = os.path.basename(subdir) # Ex: "frames"
# Vérifier si le dossier parent correspond au pattern et que nous sommes dans "frames/"
match = pattern.match(parent_folder)
if match and folder_name == "frames":
if int(match.group(1)) <int("12") :
frame_path = os.path.join(subdir, "frame-102.jpg") # Construire le chemin cible
if os.path.exists(frame_path): # Vérifier si le fichier existe
image_files.append((match.group(1), frame_path)) # Stocker (numéro dossier, chemin image)
# Trier les images par numéro de dossier (au cas où)
image_files.sort(key=lambda x: int(x[0]))
# Affichage des images avec PIL
if not image_files:
print("Aucune image 'frame-102.jpg' trouvée.")
else:
for folder_num, img_path in image_files:
print(f"Expériences : {folder_num}") # Affiche le numéro du dossier
img = Image.open(img_path) # Ouvrir l'image avec PIL
display(img) # Afficher dans Jupyter
Expériences : 1
Expériences : 2
Expériences : 3
Expériences : 4
Expériences : 5
Expériences : 6
Expériences : 7
Expériences : 8
Expériences : 9
Expériences : 10
Expériences : 11
Le météore est capturé de la même manière que précedement mais avec plus de vecteurs dessus.
6.4 - Analyse¶
Nous allons maintenant calculer le vecteur moyen de chaque image ainsi que l'écart type et la norme associé. Nous allons rentrer ces informations dans des fichiers csv afin de les analyser.
De plus on crée un fichier csv avec les paramètres de la commande utilisé pour essayer de faire une analyse de composantes connexes ou de la classification supérvisée par la suite.
Fichier vidéo totale¶
import numpy as np
import glob
import re
import os
import csv
def process_motion_vectors(file_path):
"""Calcule le vecteur moyen et l'écart type des vecteurs de mouvement dans un fichier .npy."""
motion_vectors = np.load(file_path)
if motion_vectors.shape[0] == 0:
return (0, 0), (0, 0), 0
motion_x = motion_vectors[:,5] - motion_vectors[:, 3]
motion_y = motion_vectors[:,6] - motion_vectors[:, 4]
mean_vector = (np.mean(motion_x), np.mean(motion_y))
std_vector = (np.std(motion_x), np.std(motion_y))
start_pt = motion_vectors[:, [3, 4]]
end_pt = motion_vectors[:, [5, 6]]
norm_vector = np.mean(np.linalg.norm(end_pt - start_pt, axis=1))
return mean_vector, std_vector, norm_vector
# Définir le chemin du dossier contenant les sous-dossiers
base_path = "../outputs_frames/"
results_path = os.path.join(base_path, "../results_motion_vectors") # Dossier où stocker les fichiers CSV
# Créer le dossier results s'il n'existe pas
os.makedirs(results_path, exist_ok=True)
pattern = re.compile(r"^\d+out.*$") # Ex: "123outXYZ", "456out_test"
# Lister les dossiers correspondants
folders = [f for f in os.listdir(base_path) if pattern.match(f) and os.path.isdir(os.path.join(base_path, f, "motion_vectors"))]
for folder in folders:
motion_vectors_path = os.path.join(base_path, folder, "motion_vectors")
files = glob.glob(os.path.join(motion_vectors_path, "*.npy"))
output_csv = os.path.join(results_path, f"{folder}.csv")
# Écrire les résultats dans un fichier CSV
with open(output_csv, mode="w", newline="") as csv_file:
writer = csv.writer(csv_file)
writer.writerow(["Fichier", "Vecteur Moyen X", "Vecteur Moyen Y", "Écart Type X", "Écart Type Y", "Norme"])
#print(f"📁 Traitement du dossier: {folder}")
for file in files:
mean, std, norm = process_motion_vectors(file)
writer.writerow([os.path.basename(file), mean[0], mean[1], std[0], std[1], norm])
#print(f" 📄 {os.path.basename(file)} → Moyenne: {mean}, Écart type: {std}, Norme : {norm}")
print(f"✅ Résultats enregistrés dans: {output_csv}\n")
✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/10out-2025-02-19T09:24:08.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/6out-2025-02-19T07:06:46.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/12out-2025-02-19T09:24:47.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/8out-2025-02-19T08:58:36.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/3out-2025-02-19T06:29:11.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/9out-2025-02-19T08:58:46.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/11out-2025-02-19T09:24:30.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/4out-2025-02-19T06:29:24.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/1out-2025-02-18T20:36:23.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/2out-2025-02-19T06:00:17.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/7out-2025-02-19T08:58:23.csv ✅ Résultats enregistrés dans: ../outputs_frames/../results_motion_vectors/5out-2025-02-19T07:06:37.csv
Fichier vidéo crop¶
pattern = re.compile(r"^\d+crop_out.*$")
# Lister les dossiers correspondants
folders = [f for f in os.listdir(base_path) if pattern.match(f) and os.path.isdir(os.path.join(base_path, f, "motion_vectors"))]
for folder in folders:
motion_vectors_path = os.path.join(base_path, folder, "motion_vectors")
files = glob.glob(os.path.join(motion_vectors_path, "*.npy"))
output_csv = os.path.join(results_path, f"{folder}.csv")
# Écrire les résultats dans un fichier CSV
with open(output_csv, mode="w", newline="") as csv_file:
writer = csv.writer(csv_file)
writer.writerow(["Fichier", "Vecteur Moyen X", "Vecteur Moyen Y", "Écart Type X", "Écart Type Y", "Norme"])
#print(f"📁 Traitement du dossier: {folder}")
for file in files:
mean, std, norm = process_motion_vectors(file)
writer.writerow([os.path.basename(file), mean[0], mean[1], std[0], std[1], norm])
#print(f" 📄 {os.path.basename(file)} → Moyenne: {mean}, Écart type: {std}, Norme: {norm}")
print(f"✅ Résultats enregistrés dans: {output_csv}\n")
✅ Résultats enregistrés dans: ../results_motion_vectors/8crop_out-2025-03-12T10:05:29.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/2crop_out-2025-03-12T10:04:26.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/9crop_out-2025-03-12T10:05:33.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/1crop_out-2025-03-12T10:01:32.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/6crop_out-2025-03-12T10:04:47.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/5crop_out-2025-03-12T10:04:43.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/4crop_out-2025-03-12T10:04:37.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/3crop_out-2025-03-12T10:04:31.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/10crop_out-2025-03-12T10:05:38.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/7crop_out-2025-03-12T10:04:53.csv ✅ Résultats enregistrés dans: ../results_motion_vectors/11crop_out-2025-03-12T10:05:43.csv
Fichier avec les paramètres d'encodage¶
Pour le paramètre Capture du météore ? il va falloir le remplir à la main par la suite
import os
import glob
import csv
import re
# Définition des chemins
base_path = "../outputs_frames/"
results_path = "../results_motion_vectors"
output_csv = os.path.join(results_path, "merged_results.csv")
# Métadonnées associées aux numéros de test
metadata = {
1: (30, "fast", 250, "5M", "h264", "défaut", 0),
2: (30, "fast", 10, "5M", "h264", "défaut", 0),
3: (30, "slow", 10, "5M", "h264", "défaut", 0),
4: (30, "slow", 5, "5M", "h264", "défaut", 0),
5: (25, "slow", 10, "5M", "h264", "défaut", 0),
6: (25, "p7", 10, "5M", "h264", "défaut", 0),
7: (30, "p7", 10, "variable", "h264", "yuv420p", 0),
8: (30, "p7", 10, "variable", "h264", "gray", 0),
9: (30, "slow", 15, "5M", "h264", "défaut", 0),
10: (30, "slow", 10, "5M", "h265", "défaut", 0),
11: (30, "p7", 10, "variable", "h264", "yuv444p", 0),
}
# Création du fichier CSV unique
with open(output_csv, mode="w", newline="") as csv_file:
writer = csv.writer(csv_file)
# Écriture de l'en-tête
writer.writerow([
"Numéro", "Framerate", "Preset", "Images Clés",
"Bitrate", "Encodage", "Pix_fmt",
"Dossier", "Fichier", "Vecteur Moyen X", "Vecteur Moyen Y",
"Écart Type X", "Écart Type Y", "Norme", "Capture"
])
# Définition du motif pour identifier les dossiers et extraire le numéro
pattern = re.compile(r"^(\d+)crop_out.*$")
folders = [f for f in os.listdir(base_path) if pattern.match(f) and os.path.isdir(os.path.join(base_path, f, "motion_vectors"))]
for folder in folders:
match = pattern.match(folder)
if not match:
continue
test_num = int(match.group(1)) # Extraire le numéro au début du nom du dossier
if test_num not in metadata:
continue # Ignorer si pas dans la table de métadonnées
framerate, preset, keyframes, bitrate, encoding, pix_fmt, capture = metadata[test_num]
motion_vectors_path = os.path.join(base_path, folder, "motion_vectors")
files = glob.glob(os.path.join(motion_vectors_path, "*.npy"))
#print(f"📁 Traitement du dossier: {folder} (Numéro {test_num})")
for file in files:
mean, std, norm = process_motion_vectors(file)
writer.writerow([
test_num, framerate, preset, keyframes,
bitrate, encoding, pix_fmt,
folder, os.path.basename(file), mean[0], mean[1], std[0], std[1], norm, capture
])
#print(f" 📄 {os.path.basename(file)} → Moyenne: {mean}, Écart type: {std}, Norme: {norm}")
print(f"✅ Fusion terminée. Résultats enregistrés sous : {output_csv}")
✅ Fusion terminée. Résultats enregistrés sous : ../results_motion_vectors/merged_results.csv